package ai.hou.camera.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.FloatEvaluator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;

import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.DisplayOrientedMeteringPointFactory;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.Preview;
import androidx.camera.core.ZoomState;
import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.common.HybridBinarizer;

import java.io.File;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import ai.hou.camera.R;
import ai.hou.camera.util.ZxingUtils;

/**
 * 相机
 *
 * @author zhaoxiaolei
 * @date 2021/3/10 10:32
 */
public class MagicCameraView extends FrameLayout {
    private static final String TAG = "MagicCameraView";

    private final FragmentActivity activity;

    @ColorRes
    static final int DEFAULT_BACKGROUND_COLOR = android.R.color.black;
    /**
     * 相机的比例
     * {@link AspectRatio#RATIO_4_3} or {@link AspectRatio#RATIO_16_9 is default}
     */
    private int mAspectRatio = AspectRatio.RATIO_16_9;
    /**
     * 捕获的模式
     * {@link ImageCapture#CAPTURE_MODE_MAXIMIZE_QUALITY is default} or {@link ImageCapture#CAPTURE_MODE_MINIMIZE_LATENCY}
     */
    private int mCaptureMode = ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
    /**
     * 前后摄像头
     * {@link CameraSelector#LENS_FACING_FRONT}  or {@link CameraSelector#LENS_FACING_BACK is default}
     */
    private int mLensFacing = CameraSelector.LENS_FACING_BACK;
    /**
     * 旋转角度
     * {@link Surface#ROTATION_0} or {@link Surface#ROTATION_90} or {@link Surface#ROTATION_180} or {@link Surface#ROTATION_270}
     */
    private int mRotation = ImageOutputConfig.INVALID_ROTATION;
    /**
     * 预览
     */
    private final PreviewView mPreviewView;
    private ImageView focusView;
    /**
     * 相机属性及设置
     */
    private Camera mCamera;
    private ProcessCameraProvider cameraProvider;
    private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
    private ImageCapture.Metadata metadata;
    private ImageCapture imageCapture;
    private ImageAnalysis imageAnalysis;
    private CameraSelector cameraSelector;

    /**
     * 缩放比例
     */
    private float linearZoom;
    /**
     * 缩放比例
     */
    private float zoomRatio;
    /**
     * 最小缩放比例
     */
    private float minZoomRatio;
    /**
     * 最大缩放比例
     */
    private float maxZoomRatio;

    /**
     * 是否开灯
     */
    private boolean isTorchOn;
    /**
     * 闪光灯模式
     * {@link ImageCapture#FLASH_MODE_AUTO},{@link ImageCapture#FLASH_MODE_ON},{@link ImageCapture#FLASH_MODE_OFF}
     */
    private int mFlashMode = ImageCapture.FLASH_MODE_AUTO;
    /**
     * 是否水平翻转
     */
    private boolean mIsReversedHorizontal;
    /**
     * 是否垂直翻转
     */
    private boolean mIsReversedVertical;
    /**
     * 相机是否初始化
     */
    private boolean mIsCameraInit;
    /**
     * 是否识别二维码
     */
    private boolean decodeQrCodeRunning;
    /**
     * 是否自定义尺寸
     */
    private boolean isCustomSize;
    private Size mCustomSize;
    private int mPreviewGravity = Gravity.TOP;
    /**
     * LocationProvider
     * {@link LocationManager#GPS_PROVIDER}, {@link LocationManager#NETWORK_PROVIDER} or {@link LocationManager#PASSIVE_PROVIDER}
     */
    private String mLocationProvider = LocationManager.PASSIVE_PROVIDER;
    private Executor mainExecutor;
    private Executor threadExecutor;
    private DisplayMetrics mMetrics;
    private Location location;

    private ZxingUtils.DecodeQrCodeListener decodeQrCodeListener;

    private final ImageAnalysis.Analyzer mAnalyzer = imageProxy -> {
        //这里做分析使用
        if (decodeQrCodeListener == null || !decodeQrCodeRunning) {
            imageProxy.close();
            return;
        }
        ImageProxy.PlaneProxy[] planes = imageProxy.getPlanes();
        int width = imageProxy.getWidth();
        int height = imageProxy.getHeight();
        // 转为byte[]
        ByteBuffer buffer = planes[0].getBuffer();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        int pixelStride = planes[0].getPixelStride();
        int rowStride = planes[0].getRowStride();
        int rowPadding = rowStride - pixelStride * width;

        BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new PlanarYUVLuminanceSource(bytes, width + rowPadding / pixelStride, height, 0, 0, width, height, false)));

        ZxingUtils.decodeQrCode(binaryBitmap, new ZxingUtils.DecodeQrCodeListener() {
            @Override
            public void onSuccess(String text) {
//                Log.d(TAG, "setAnalyzer-onSuccess-二维码解析成功: " + text);
                decodeQrCodeRunning = false;
                decodeQrCodeListener.onSuccess(text);
                imageProxy.close();
            }

            @Override
            public void onFailed() {
//                Log.d(TAG, "setAnalyzer-onFailed-二维码解析失败");
                decodeQrCodeListener.onFailed();
                imageProxy.close();
            }
        });
    };

    private final ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1;
    private ObjectAnimator focusAnimator;

    public MagicCameraView(Context context) {
        this(context, null);
    }

    public MagicCameraView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MagicCameraView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        if (context instanceof FragmentActivity) {
            activity = (FragmentActivity) context;
        } else {
            throw new IllegalStateException("只能在继承自FragmentActivity的activity中使用");
        }

        mPreviewView = new PreviewView(getContext());
        addView(mPreviewView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        focusView = new ImageView(getContext());
        focusView.setImageResource(R.drawable.ic_focus);
        int focusSize = getResources().getDimensionPixelOffset(R.dimen.focus_view_size);
        addView(focusView, new ViewGroup.LayoutParams(focusSize, focusSize));
        focusView.setVisibility(INVISIBLE);

        if (getBackground() == null) {
            setBackgroundColor(ContextCompat.getColor(getContext(), DEFAULT_BACKGROUND_COLOR));
        }

        ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
                mScaleFactor *= scaleGestureDetector.getScaleFactor();
                Log.i(TAG, "onScale: " + mScaleFactor + "----" + maxZoomRatio);
                if (mScaleFactor / maxZoomRatio > 1) {
                    return true;
                }
                setLinearZoom(mScaleFactor / maxZoomRatio);
                return true;
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                return true;
            }
        };
        mScaleDetector = new ScaleGestureDetector(context, mScaleGestureListener);
    }

    /**
     * 在获取权限后第一时间初始化
     */
    public void initCameraView() {
        cameraProviderFuture = ProcessCameraProvider.getInstance(getContext());
        mainExecutor = ContextCompat.getMainExecutor(activity);
        threadExecutor = Executors.newCachedThreadPool();
        cameraProviderFuture.addListener(() -> {
            try {
                cameraProvider = cameraProviderFuture.get();
                initAspectRatio();
                bindPreview();
            } catch (ExecutionException | InterruptedException e) {
                // No errors need to be handled for this Future.
                // This should never be reached.
            }
        }, mainExecutor);

        setMetadata();
    }

    private void initAspectRatio() {
        mMetrics = new DisplayMetrics();
        mPreviewView.getDisplay().getRealMetrics(mMetrics);
        mAspectRatio = aspectRatio(mMetrics.widthPixels, mMetrics.heightPixels);
        Log.d(TAG, "Screen metrics: " + mMetrics.widthPixels + " X " + mMetrics.heightPixels + "，默认的mAspectRatio：" + mAspectRatio);
    }

    private void bindPreview() {
        Log.d(TAG, "bindPreview = cameraProvider: " + cameraProvider);
        if (cameraProvider == null) {
            return;
        }
        //设置view的宽高
        FrameLayout.LayoutParams layoutParams = (LayoutParams) mPreviewView.getLayoutParams();
        layoutParams.gravity = mPreviewGravity;
        if (isCustomSize && mCustomSize != null) {
            layoutParams.width = mCustomSize.getWidth();
            layoutParams.height = mCustomSize.getHeight();
        } else {
            if (mAspectRatio == AspectRatio.RATIO_4_3) {
                layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
                layoutParams.height = mMetrics.widthPixels * 4 / 3;
            } else {
                layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
                layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
            }
        }
        mPreviewView.setLayoutParams(layoutParams);
        //预览的配置
        Preview.Builder builder = new Preview.Builder();
        if (isCustomSize && mCustomSize != null) {
            builder.setTargetResolution(mCustomSize);
        } else {
            builder.setTargetAspectRatio(mAspectRatio);
        }
        Preview preview = builder.build();
        //相机选择配置
        cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(mLensFacing)
                .build();
        //图片抓取配置
        ImageCapture.Builder imageCaptureBuilder = new ImageCapture.Builder();
        if (isCustomSize && mCustomSize != null) {
            imageCaptureBuilder.setTargetResolution(mCustomSize);
        } else {
            imageCaptureBuilder.setTargetAspectRatio(mAspectRatio);
        }
        imageCaptureBuilder
                .setCaptureMode(mCaptureMode)
                .setTargetRotation(getTargetRotation())
                .setFlashMode(mFlashMode);
        imageCapture = imageCaptureBuilder.build();
        //图像分析配置
        ImageAnalysis.Builder imageAnalysisBuilder = new ImageAnalysis.Builder();
        if (isCustomSize && mCustomSize != null) {
            imageAnalysisBuilder.setTargetResolution(mCustomSize);
        } else {
            imageAnalysisBuilder.setTargetAspectRatio(mAspectRatio);
        }
        imageAnalysis = imageAnalysisBuilder.build();
        imageAnalysis.setAnalyzer(threadExecutor, mAnalyzer);
        //绑定
        cameraProvider.unbindAll();
        mCamera = cameraProvider.bindToLifecycle(activity, cameraSelector, preview, imageCapture, imageAnalysis);
        preview.setSurfaceProvider(mPreviewView.createSurfaceProvider());
        mCamera.getCameraInfo().getZoomState().observe(activity, zoomState -> {
            zoomRatio = zoomState.getZoomRatio();
            minZoomRatio = zoomState.getMinZoomRatio();
            maxZoomRatio = zoomState.getMaxZoomRatio();
            Log.d(TAG, "bindPreview: " + linearZoom + "====" + zoomRatio);
        });
        //点击聚焦
        mPreviewView.setOnTouchListener((v, event) -> {
            mScaleDetector.onTouchEvent(event);
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                //聚焦
                startFocusAndMetering(event.getX(), event.getY());
                //聚焦动画
                showFocusView((int) event.getX(), (int) event.getY());
            }
            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                return true;
            }
            return false;
        });
        //这里开始mCamera才不为空
        mIsCameraInit = true;
        if (mCallback != null) {
            mCallback.onCameraInit();
        }
    }

    private void showFocusView(int x, int y) {
        if (focusAnimator == null) {
            PropertyValuesHolder propertyValuesHolderX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1f, 1.1f, 0.7f);
            PropertyValuesHolder propertyValuesHolderY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f, 1.1f, 0.7f);
            focusAnimator = ObjectAnimator.ofPropertyValuesHolder(focusView, propertyValuesHolderX, propertyValuesHolderY);
            focusAnimator.setEvaluator(new FloatEvaluator());
            focusAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    focusView.setVisibility(INVISIBLE);
                }

                @Override
                public void onAnimationStart(Animator animation) {
                    focusView.setVisibility(VISIBLE);
                }
            });
            focusAnimator.setDuration(800);
            focusAnimator.setStartDelay(200);
        } else {
            if (focusAnimator.isRunning()) {
                return;
            }
        }
        int radius = focusView.getWidth() / 2;
        focusView.layout(x - radius, y - radius, x + radius, y + radius);
        focusAnimator.start();
    }

    private void setMetadata() {
        if (metadata == null) {
            metadata = new ImageCapture.Metadata();
        }
        if (location == null) {
            location = new Location(mLocationProvider);
        }
        if (!Objects.equals(location.getProvider(), mLocationProvider)) {
            location.setProvider(mLocationProvider);
        }
        metadata.setLocation(location);
        metadata.setReversedHorizontal(mIsReversedHorizontal);
        metadata.setReversedVertical(mIsReversedVertical);
    }

    public void takePicture(@NonNull File photoFile, final @NonNull ImageCapture.OnImageSavedCallback imageSavedCallback) {
        setMetadata();
        ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(photoFile)
                .setMetadata(metadata)
                .build();
        imageCapture.takePicture(outputOptions, mainExecutor, imageSavedCallback);
    }

    public void takePicture(@NonNull OutputStream outputStream, final @NonNull ImageCapture.OnImageSavedCallback imageSavedCallback) {
        setMetadata();
        ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(outputStream)
                .setMetadata(metadata)
                .build();
        imageCapture.takePicture(outputOptions, mainExecutor, imageSavedCallback);
    }

    public @ImageOutputConfig.RotationValue
    int getTargetRotation() {
        switch (mRotation) {
            case Surface.ROTATION_0:
                return Surface.ROTATION_0;
            case Surface.ROTATION_90:
                return Surface.ROTATION_90;
            case Surface.ROTATION_180:
                return Surface.ROTATION_180;
            case Surface.ROTATION_270:
                return Surface.ROTATION_270;
            default:
                return mPreviewView.getDisplay().getRotation();
        }
    }

    public boolean hasFontCamera() {
        try {
            return cameraProvider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA);
        } catch (CameraInfoUnavailableException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean hasBackCamera() {
        try {
            return cameraProvider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA);
        } catch (CameraInfoUnavailableException e) {
            e.printStackTrace();
        }
        return false;
    }

    private void verifyCamera() {
        if (!mIsCameraInit) {
            throw new IllegalStateException("Camera还未初始化，请在初始化后再调，或者在setCallback()回调中使用");
        }
    }

    public Camera getCamera() {
        verifyCamera();
        return mCamera;
    }

    public CameraInfo getCameraInfo() {
        verifyCamera();
        return mCamera.getCameraInfo();
    }

    public LiveData<Integer> getTorchState() {
        verifyCamera();
        return mCamera.getCameraInfo().getTorchState();
    }

    public LiveData<ZoomState> getZoomState() {
        verifyCamera();
        return mCamera.getCameraInfo().getZoomState();
    }

    public int getSensorRotationDegrees() {
        verifyCamera();
        return mCamera.getCameraInfo().getSensorRotationDegrees();
    }

    public Size getPreviewSize() {
        return new Size(mPreviewView.getMeasuredWidth(), mPreviewView.getMeasuredHeight());
    }

    /**
     * 开灯
     */
    public void setTorchOn() {
        verifyCamera();
        isTorchOn = true;
        mCamera.getCameraControl().enableTorch(true);
    }

    /**
     * 关灯
     */
    public void setTorchOff() {
        verifyCamera();
        isTorchOn = false;
        mCamera.getCameraControl().enableTorch(false);
    }

    /**
     * 切换灯光
     */
    public void toggleTorch() {
        if (isTorchOn) {
            setTorchOff();
        } else {
            setTorchOn();
        }
    }

    /**
     * 设置闪光灯模式
     *
     * @param flashMode {@link ImageCapture#FLASH_MODE_AUTO},{@link ImageCapture#FLASH_MODE_ON},{@link ImageCapture#FLASH_MODE_OFF}
     */
    public void setFlashMode(@ImageCapture.FlashMode int flashMode) {
        this.mFlashMode = flashMode;
        imageCapture.setFlashMode(flashMode);
    }

    public float getZoomRatio() {
        return zoomRatio;
    }

    public float getMinZoomRatio() {
        return minZoomRatio;
    }

    public float getMaxZoomRatio() {
        return maxZoomRatio;
    }

    /**
     * 设置相机缩放比例
     *
     * @param ratio 相机缩放比例
     */
    public void setZoomRatio(float ratio) {
        if (ratio < minZoomRatio) {
            zoomRatio = minZoomRatio;
        } else if (ratio > maxZoomRatio) {
            zoomRatio = maxZoomRatio;
        } else {
            zoomRatio = ratio;
        }
        verifyCamera();
        mCamera.getCameraControl().setZoomRatio(zoomRatio);
    }

    /**
     * 设置相机缩放比例
     *
     * @param ratio 相机缩放比例
     */
    public void setLinearZoom(float ratio) {
        if (ratio < 0) {
            linearZoom = 0;
        } else if (ratio > 1) {
            linearZoom = 1;
        } else {
            linearZoom = ratio;
        }
        verifyCamera();
        mCamera.getCameraControl().setLinearZoom(linearZoom);
    }

    public void setTargetAspectRatio(@AspectRatio.Ratio int aspectRatio) {
        this.mAspectRatio = aspectRatio;
        bindPreview();
    }

    public void setCaptureMode(@ImageCapture.CaptureMode int captureMode) {
        this.mCaptureMode = captureMode;
        bindPreview();
    }

    public void requireLensFacing(@CameraSelector.LensFacing int lensFacing) {
        this.mLensFacing = lensFacing;
        bindPreview();
    }

    public int getLensFacing() {
        return mLensFacing;
    }

    public void setTargetRotation(@ImageOutputConfig.RotationValue int rotation) {
        this.mRotation = rotation;
        imageCapture.setTargetRotation(rotation);
    }

    public void setReversedHorizontal(boolean isReversedHorizontal) {
        this.mIsReversedHorizontal = isReversedHorizontal;
        setMetadata();
    }

    public boolean ismIsReversedHorizontal() {
        return mIsReversedHorizontal;
    }

    public void setReversedVertical(boolean isReversedVertical) {
        this.mIsReversedVertical = isReversedVertical;
        setMetadata();
    }

    public boolean ismIsReversedVertical() {
        return mIsReversedVertical;
    }

    /**
     * @param provider {@link LocationManager#GPS_PROVIDER}, {@link LocationManager#NETWORK_PROVIDER},
     *                 or {@link LocationManager#PASSIVE_PROVIDER}.
     */
    public void setLocationProvider(String provider) {
        this.mLocationProvider = provider;
        setMetadata();
    }

    public void setIsCustomSize(boolean isCustomSize) {
        this.isCustomSize = isCustomSize;
        bindPreview();
    }

    public void setCustomSize(int width, int height) {
        mCustomSize = new Size(width, height);
        bindPreview();
    }

    public void setPreviewGravity(int gravity) {
        this.mPreviewGravity = gravity;
        bindPreview();
    }

    /**
     * 测光聚焦
     *
     * @param x 点击的x位置
     * @param y 点击的y位置
     */
    public void startFocusAndMetering(float x, float y) {
        verifyCamera();
        try {
            DisplayOrientedMeteringPointFactory pointFactory = new DisplayOrientedMeteringPointFactory(mPreviewView.getDisplay(), cameraSelector, mPreviewView.getWidth(), mPreviewView.getHeight());
            MeteringPoint meteringPoint = pointFactory.createPoint(x, y);
            FocusMeteringAction action = new FocusMeteringAction.Builder(meteringPoint)
                    .build();
            mCamera.getCameraControl().startFocusAndMetering(action);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 重新开始分析
     */
    public void restartAnalyzer() {
        decodeQrCodeRunning = true;
    }

    private static final double RATIO_4_3_VALUE = 4.0 / 3.0;
    private static final double RATIO_16_9_VALUE = 16.0 / 9.0;

    private int aspectRatio(int width, int height) {
        double previewRatio = 1.0 * Math.max(width, height) / Math.min(width, height);
        if (Math.abs(previewRatio - RATIO_4_3_VALUE) <= Math.abs(previewRatio - RATIO_16_9_VALUE)) {
            return AspectRatio.RATIO_4_3;
        }
        return AspectRatio.RATIO_16_9;
    }

    public interface CameraInitCallback {
        void onCameraInit();
    }

    private CameraInitCallback mCallback;

    public void setCallback(CameraInitCallback callback) {
        this.mCallback = callback;
    }

    private boolean isDestroyed;

    public boolean isDestroyed() {
        return isDestroyed;
    }

    public void onDestroy() {
        if (cameraProvider!=null) {
            cameraProvider.unbindAll();
        }
        isDestroyed = true;
    }

    public void onCreate() {
        bindPreview();
        isDestroyed = false;
    }

    public void setDecodeQrCodeListener(ZxingUtils.DecodeQrCodeListener decodeQrCodeListener) {
        decodeQrCodeRunning = true;
        this.decodeQrCodeListener = decodeQrCodeListener;
    }
}
